package prog1;

import prog1.common.Defs;
import prog1.common.Parameters;
import prog1.common.Utils;
import prog1.helpers.CipherBuilder;
import prog1.helpers.KeyStoreProxy;
import prog1.helpers.RandomIV;
import prog1.helpers.SignatureProxy;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.Key;
import java.security.PublicKey;

/**
 * Created with IntelliJ IDEA.
 * User: Roi
 * Date: 04/12/13
 *
 * This application decrypts an encrypted file and verifies its authenticity
 * (using parameters file - Defs.PARAMETERS_FILENAME)
 *
 * Application arguments: key store password
 *
 * One file is created:
 * - Decrypted file (Defs.Defaults.DECRYPTED_FILENAME)
 */
public class Decrypt {
    /**
     * Main method
     * @param args program arguments
     */
    public static void main(String[] args) {
        try {
            Utils.log("Loading parameters from file: " + Defs.PARAMETERS_FILENAME);

            // Initialize parameters, de-serialize from parameters file
            Parameters parameters = new Parameters();
            parameters.deserialize();

            // Retrieve key store password
            Utils.log("Reading key store password from program arguments");
            String keyStorePassword = Utils.getKeyStorePassword(args);

            // Initialize and load key store
            Utils.log("Initializing key store");
            KeyStoreProxy keyStoreProxy = new KeyStoreProxy();
            keyStoreProxy.initialize(parameters.get("KEY_STORE_TYPE"), parameters.get("KEY_STORE_PROVIDER"));
            keyStoreProxy.load(parameters.get("KEY_STORE_FILE_NAME"), keyStorePassword);

            // Retrieve encrypted key & private key from key store
            Key encryptedKey = keyStoreProxy.getKey(parameters.get("FILE_ENCRYPTION_ALGORITHM"), keyStorePassword);
            Key privateKey = keyStoreProxy.getKey(parameters.get("KEY_STORE_KEY_ENCRYPTION_ALGORITHM_ALIAS"), keyStorePassword);

            // Initialize cipher for key decryption
            Utils.log("Initializing cipher for key decryption");
            Cipher keyCipher = new CipherBuilder()
                    .withAlgorithm(parameters.get("KEY_ENCRYPTION_ALGORITHM"))
                    .withProvider(parameters.get("KEY_ENCRYPTION_PROVIDER"))
                    .withMode(Cipher.DECRYPT_MODE)
                    .withKey(privateKey)
                    .build();

            // Get decrypted key
            Utils.log("Decrypting key");
            byte[] decryptedKey = keyCipher.doFinal(encryptedKey.getEncoded());

            // Create decrypted secret key
            SecretKey decryptedSecretKey = new SecretKeySpec(decryptedKey, parameters.get("FILE_ENCRYPTION_ALGORITHM"));

            // De-serialize random IV
            RandomIV randomIV = new RandomIV();
            randomIV.deserialize(parameters);

            // Initialize cipher for file decryption
            Utils.log("Initializing cipher for file decryption");
            Cipher fileCipher = new CipherBuilder()
                    .withAlgorithm(parameters.get("FILE_ENCRYPTION_TRANSFORMATION"))
                    .withProvider(parameters.get("FILE_ENCRYPTION_PROVIDER"))
                    .withMode(Cipher.DECRYPT_MODE)
                    .withKey(decryptedSecretKey)
                    .withSpec(new IvParameterSpec(randomIV.getRandomIV()))
                    .build();

            // Create cipher input stream from encrypted file
            Utils.log("Decrypting file: " + parameters.get("ENCRYPTED_FILENAME"));
            CipherInputStream cis = new CipherInputStream(new FileInputStream(parameters.get("ENCRYPTED_FILENAME")), fileCipher);

            // Open output stream for decrypted file
            FileOutputStream fos = new FileOutputStream(parameters.get("DECRYPTED_FILENAME"));

            // Read and decrypt encrypted file per block
            byte[] block = new byte[Defs.BLOCK_SIZE];
            int index;
            while ((index = cis.read(block)) != -1) {
                fos.write(block, 0, index);
            }
            fos.close();
            cis.close();

            Utils.log("Decrypted file saved to: " + parameters.get("DECRYPTED_FILENAME"));

            // Get public key from key store
            PublicKey publicKey = keyStoreProxy.getPublicKey(parameters.get("KEY_STORE_SIGNATURE_ALGORITHM_ALIAS"));

            // De-serialize digital signature from parameters file, initialize with public key
            Utils.log("Loading digital signature");
            SignatureProxy signatureProxy =  new SignatureProxy(parameters.get("SIGNATURE_ALGORITHM"),
                    parameters.get("SIGNATURE_PROVIDER"));
            signatureProxy.deserialize(parameters);
            signatureProxy.getSignature().initVerify(publicKey);

            // Create input stream of decrypted file
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(parameters.get("DECRYPTED_FILENAME")));

            // Read decrypted file and update signature object for verification
            Utils.log("Verifying data integrity using signature");
            byte[] buffer = new byte[Defs.BUFFER_SIZE];
            int length;
            while (bis.available() != 0) {
                length = bis.read(buffer);
                signatureProxy.getSignature().update(buffer, 0, length);
            }
            bis.close();

            // Verify signature
            boolean isVerified = signatureProxy.verify();
            Utils.log("Signature verified = " + isVerified);

            Utils.log("Finished");
        } catch(Exception e) {
            System.out.println("An error has occurred: " + e.getMessage());
        }
    }
}
